Skip to content

X Tracking Pixel - #59

Merged
mikaalnaik merged 2 commits into
mainfrom
mikaal/x-pixel
Aug 20, 2026
Merged

X Tracking Pixel#59
mikaalnaik merged 2 commits into
mainfrom
mikaal/x-pixel

Conversation

@mikaalnaik

@mikaalnaik mikaalnaik commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Adds the X (Twitter) Ads universal website tag so we can attribute conversions from X ad campaigns.

What's here

  • src/components/XPixel.tsx — the tag, pixel ID re2t6, loaded afterInteractive.
  • src/app/layout.tsx — mounted once in the root layout.

Production only (NODE_ENV guard), so localhost and preview deployments stay out of the ad data.

Client-side navigation

uwt.js installs no history listener — the source has no references to pushState, replaceState, popstate, or hashchange — so the tag reports only the initial page load and every App Router navigation would go unreported.

PageViewTracker handles this the same way HubspotTracking already does: watch usePathname()/useSearchParams() and report each route change, skipping the first render since the inline snippet already covered it.

Route changes re-call twq('config', ...) rather than the legacy twq('track', 'PageView'). Both APIs live in uwt.js — the legacy UWT (init/track) and the current X Pixel (config/event/set) — and this pixel uses the latter. Re-calling config is safe and correct: its implementation unconditionally invokes l.track({… eci: ONETAG_CONFIG …}) with no init-once guard, so every invocation emits a fresh page-load beacon.

Notes for review

No CSP change needed. public/_headers has a script-src policy that omits static.ads-twitter.com, but that file is dead config here — it's a Cloudflare Pages/Netlify convention and this app deploys via Docker, which never reads it. next.config.ts already documents this, setting Content-Signal as a real Next.js header "rather than public/_headers because the Docker deploy doesn't read that file." The same allowlist also omits googletagmanager, hs-scripts, and PostHog, all of which work in production today. Deleting that stale file or porting its CSP into next.config.ts is worth a separate PR; the ads-twitter origin should be included if that happens.

Consent gating is out of scope. The privacy notice says we rely on consent that can be withdrawn, and there's no consent mechanism in the repo — but this PR doesn't introduce that gap. GA, HubSpot, and PostHog all already load unconditionally on every production page view, gated only on NODE_ENV, and this pixel follows the identical pattern. Gating only the X pixel would leave three trackers firing pre-consent while implying it was solved. A consent banner and preference store covering all four belongs in its own PR.

Testing

tsc --noEmit and eslint are clean, and the app compiles.

Not verified end-to-end: the NODE_ENV guard makes the pixel a no-op locally, so the route-change beacon is verified by reading the tag source, not by observing a live request. Worth a spot-check in the X Ads event manager after deploy.

Unrelated: next build currently fails prerendering /tracker because the elections API 404s locally. Confirmed pre-existing — the same failure reproduces on main with this branch's changes stashed.

@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds the X Ads universal website tag globally and now reports pageviews after App Router navigations.

  • Mounts the production-only pixel from the root layout.
  • Loads and initializes the X Ads script.
  • Tracks pathname and query-string changes while avoiding duplicate initial pageviews.

Confidence Score: 2/5

The PR is not safe to merge until the X pixel is permitted by the production CSP and tracking is gated by a consent preference.

The navigation-pageview fix is present, but Cloudflare Pages still blocks the third-party script and the globally mounted pixel still initializes and reports route changes without consulting a consent or withdrawal state.

Files Needing Attention: src/components/XPixel.tsx, src/app/layout.tsx, public/_headers

Important Files Changed

Filename Overview
src/app/layout.tsx Globally mounts the X pixel, but the previously reported consent boundary remains unenforced.
src/components/XPixel.tsx Adds effective client-navigation reporting, while the production CSP and consent issues remain unresolved.

Reviews (2): Last reviewed commit: "Report X pixel page views on client navi..." | Re-trigger Greptile

Comment thread src/app/layout.tsx
Comment thread src/components/XPixel.tsx
Comment thread src/app/layout.tsx
<SubscribeModal />
<IdentifyUser />
<HubspotTracking />
<XPixel />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Advertising pixel bypasses consent

When any visitor opens a production route, the globally mounted pixel reports the initial page view without consulting a consent preference, so visitors cannot withhold or withdraw the consent promised by the privacy notice. How this was verified: The root layout mounts XPixel unconditionally, its only guard checks NODE_ENV, and the repository contains no consent or opt-out mechanism.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/layout.tsx
Line: 81

Comment:
**Advertising pixel bypasses consent**

When any visitor opens a production route, the globally mounted pixel reports the initial page view without consulting a consent preference, so visitors cannot withhold or withdraw the consent promised by the privacy notice. **How this was verified:** The root layout mounts `XPixel` unconditionally, its only guard checks `NODE_ENV`, and the repository contains no consent or opt-out mechanism.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real gap, but pre-existing and out of scope for this PR.

You are right that the privacy notice says "We rely on your consent, which you can withdraw" (src/app/privacy-notice/page.tsx:32) and there is no consent mechanism in the repo. But that is not something this PR introduces: GA, HubSpot, and PostHog all already load unconditionally on every production page view, gated only on NODE_ENV. The X pixel follows the exact same existing pattern.

Gating only the X pixel would be incoherent — it would leave three other trackers firing pre-consent while implying the problem was solved. A consent banner plus a preference store that gates all four belongs in its own PR. Flagged to the team rather than fixed here.

uwt.js installs no history listener, so App Router route changes went
unreported. Mirror the HubSpot tracker: watch pathname/searchParams and
re-call twq('config', ...), which fires a fresh page-load beacon on every
invocation (no init-once guard in the tag).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mikaalnaik
mikaalnaik merged commit 967054c into main Aug 20, 2026
1 of 2 checks passed
@mikaalnaik mikaalnaik mentioned this pull request Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant